package hu.gar.core.dao.impl;

import hu.gar.core.dao.BaseConvertDao;
import hu.gar.core.dao.GenericDao;
import hu.gar.core.entity.BaseEntity;
import hu.gar.vo.BaseVO;

import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

public abstract class GenericDaoImpl<T extends BaseEntity, I, D extends BaseVO>
		implements GenericDao<T, I>, BaseConvertDao<T, D> {

	private static final long serialVersionUID = -4857181243796486995L;

	@PersistenceContext
	protected EntityManager em;

	private Class<T> type;

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public GenericDaoImpl() {
		Type t = getClass().getGenericSuperclass();
		ParameterizedType pt = (ParameterizedType) t;
		type = (Class) pt.getActualTypeArguments()[0];
	}

	@Override
	public long countAll(final Map<String, Object> params) {

		final StringBuffer queryString = new StringBuffer(
				"SELECT count(o) from ");

		queryString.append(type.getSimpleName()).append(" o ");
		queryString.append(this.getQueryClauses(params, null));

		final Query query = this.em.createQuery(queryString.toString());

		return (Long) query.getSingleResult();

	}

	@Override
	public T create(final T t) {
		this.em.persist(t);
		return t;
	}

	@Override
	public void delete(final I id) {
		this.em.remove(this.em.getReference(type, id));
	}

	@Override
	public T find(final I id) {
		return (T) this.em.find(type, id);
	}

	@Override
	public T update(final T t) {
		return this.em.merge(t);
	}

	@Override
	public List<T> findAll() {
		final StringBuffer queryString = new StringBuffer("SELECT o from ");

		queryString.append(type.getSimpleName()).append(" o ");

		final Query query = em.createQuery(queryString.toString());
		// final Query query = this.em.createQuery(queryString.toString());

		List<T> resultList = query.getResultList();
		// em.close();
		return resultList;
	}

	private String getQueryClauses(final Map<String, Object> params,
			final Map<String, Object> orderParams) {
		final StringBuffer queryString = new StringBuffer();
		if ((params != null) && !params.isEmpty()) {
			queryString.append(" where ");
			for (final Iterator<Map.Entry<String, Object>> it = params
					.entrySet().iterator(); it.hasNext();) {
				final Map.Entry<String, Object> entry = it.next();
				if (entry.getValue() instanceof Boolean) {
					queryString.append(entry.getKey()).append(" is ")
							.append(entry.getValue()).append(" ");
				} else {
					if (entry.getValue() instanceof Number) {
						queryString.append(entry.getKey()).append(" = ")
								.append(entry.getValue());
					} else {
						// string equality
						queryString.append(entry.getKey()).append(" = '")
								.append(entry.getValue()).append("'");
					}
				}
				if (it.hasNext()) {
					queryString.append(" and ");
				}
			}
		}
		if ((orderParams != null) && !orderParams.isEmpty()) {
			queryString.append(" order by ");
			for (final Iterator<Map.Entry<String, Object>> it = orderParams
					.entrySet().iterator(); it.hasNext();) {
				final Map.Entry<String, Object> entry = it.next();
				queryString.append(entry.getKey()).append(" ");
				if (entry.getValue() != null) {
					queryString.append(entry.getValue());
				}
				if (it.hasNext()) {
					queryString.append(", ");
				}
			}
		}
		return queryString.toString();
	}

	private T toEntityGen(D vo) {
		try {
			Constructor constructor = type.getConstructor();

			T t = (T) constructor.newInstance();

			Authentication auth = SecurityContextHolder.getContext()
					.getAuthentication();

			if (auth != null) {

				String username = (String) SecurityContextHolder.getContext()
						.getAuthentication().getPrincipal();

				if (vo.getId() != null) {
					t.setModUserId(username);
				} else {
					t.setRecUserId(username);
				}
			} else {
				if (vo.getId() != null) {
					t.setModUserId("system");
				} else {
					t.setRecUserId("system");
				}
			}

			if (vo.getId() != null) {
				t.setModDate(Calendar.getInstance().getTime());
			} else {
				t.setRecDate(Calendar.getInstance().getTime());
			}
			t.setId(vo.getId());

			return t;
		} catch (Exception e) {

			e.printStackTrace();
		}

		return null;

	}

	public D toVOGen(T entity) {
		try {
			Type t = getClass().getGenericSuperclass();
			ParameterizedType pt = (ParameterizedType) t;
			Class voType = (Class) pt.getActualTypeArguments()[2];
			Constructor constructor = voType.getConstructor();

			D d = (D) constructor.newInstance();

			d.setId(entity.getId());
			d.setModDate(entity.getModDate());
			d.setRecDate(entity.getRecDate());
			d.setRecUserId(entity.getRecUserId());
			d.setModUserId(entity.getModUserId());
			return d;
		} catch (Exception e) {

			e.printStackTrace();
		}

		return null;

	}

	public List<D> toVO(List<T> entity) {
		List<D> list = new ArrayList<D>();
		for (T e : entity) {
			D v = toVO(e);
			list.add(toVOImpl(v, e));
		}
		return list;
	}

	public List<T> toEntity(List<D> vo) {
		List<T> list = new ArrayList<T>();
		for (D v : vo) {
			T e = toEntity(v);
			list.add(toEntityImpl(e, v));
		}
		return list;
	}

	public abstract T toEntityImpl(T entity, D vo);

	public abstract D toVOImpl(D vo, T entity);

	@Override
	public D toVO(T entity) {
		if (entity == null) {
			return null;
		}
		D voGen = toVOGen(entity);
		return toVOImpl(voGen, entity);
	}

	@Override
	public T toEntity(D vo) {
		if (vo == null) {
			return null;
		}
		T entityGen = toEntityGen(vo);
		return toEntityImpl(entityGen, vo);
	}
}